/** @file    HeyuBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: HeyuBundle.cpp 2884 2010-08-10 00:31:45Z alex $
 *  @brief
 * File containing methods for the wosh::services::HeyuBundle class.
 * The header for this class can be found in HeyuBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "HeyuBundle.h"

 #include "HeyuBinding.h"

 #include <core/ObjectAllocator.h>
 #include <core/MessageFilter.h>
 #include <core/Response.h>
 #include <core/Fact.h>
 #include <core/Table.h>
 #include <core/MethodsCommon.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/DeviceManager.h>
 #include <core/WoshKernel.h>
 #include <core/SecurityManager.h>


using namespace std;
namespace wosh {
 namespace services {

 WOSH_REGISTER(wosh::services::HeyuBundle, "wosh::interfaces::services::Heyu", _Heyu_VERSION, _static_HeyuBundle1 )
 WOSH_REGISTER(wosh::services::HeyuBundle, "wosh::BundleGeneric", _Heyu_VERSION, _static_HeyuBundle2 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

HeyuBundle::HeyuBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _Heyu_NAME, false );
	Properties.update( _KEY_Version, _Heyu_VERSION );

//Log.setLevel(LOG_VERBOSE);

	Log(LOG_DEBUG, " Configuring HeyuBinding worker.." );
	this->heyuBinding = new HeyuBinding(*this);
	this->heyuBinding->setHeyuBindingListener( this );
	this->heyuBinding->setHeyuBinary( _Heyu_DEFAULT_heyubin );
	this->heyuBinding->setRefreshFrequency( 5 );
	this->heyuBinding->setAutoReloadAliases( 300 );

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _Heyu_KEY_HeyuStatus,		"",	Permission( Permission::Read) );
	Properties.set( _Heyu_KEY_HeyuVersion,		"",	Permission( Permission::Read) );
	Properties.set( _Heyu_KEY_HeyuConfigFile,	"",	Permission( Permission::Read) );
	Properties.set( _Heyu_KEY_HeyuPowerLineInt,	"",	Permission( Permission::Read) );
	Properties.set( _Heyu_KEY_HeyuBin,			_Heyu_DEFAULT_heyubin,	Permission::RW_RW_R__() );
	Properties.set( _Heyu_KEY_BindAliasesAsName,true,	Permission::RW_RW_R__() );
	Properties.set( _Heyu_KEY_AutoReloadAliases,300,	Permission::RW_RW_R__() );
	Properties.set( _Heyu_KEY_RegisterAll,		true,	Permission::RW_RW_R__() );
	Properties.set( _Heyu_KEY_RefreshFrequency,	1000,	Permission::RW_RW_R__() );
	Properties.monitor(_Heyu_KEY_HeyuBin);
	Properties.monitor(_Heyu_KEY_AutoReloadAliases);
	Properties.monitor(_Heyu_KEY_RefreshFrequency);
	updateHeyuProps();

	Log(LOG_DEBUG, " Registering methods.." );
	MethodList* mmListDevs = Methods.create<MethodList>( _METHOD_List, "List X10 Devices", Permission(Permission::RX) );
	mmListDevs->setDataFieldExtractor( DataFieldExtractor<DeviceX10>::createFor< DataAdapterListT<DeviceX10> >(this->heyuBinding->getDevices(), "X10 Devices") );

	MethodRequest* mmUpdate = Methods.create<MethodRequest>( _Heyu_METHOD_update_cache, "force update", Permission::R_XR_XR__() );
	mmUpdate->setMethod( this, (MethodRequestPtr)&HeyuBundle::mmDoUpdate );

	MethodRequest* mmEvalCommand = Methods.create<MethodRequest>( _Heyu_METHOD_eval_cmd, "run Heyu command", Permission::R_XR_XR__() );
	mmEvalCommand->setMethod( this, (MethodRequestPtr)&HeyuBundle::mmDoEvalCommand );

	MethodWRESULT* mmReloadDevices = Methods.create<MethodWRESULT>( _Heyu_METHOD_reload, "reload devices", Permission::R_XR_XR__() );
	mmReloadDevices->setMethod( this, (MethodWRESULTPtr)&HeyuBundle::reloadHeyuDevices );

	setBundleState(Bundle::STATE_CREATED, false);
 }


HeyuBundle::~HeyuBundle() {
	Log(LOG_VERBOSE, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~HeyuBundle() : Destroying while Bundle is running! Trying to stop.." );
		bundleStop();
	 }
	if ( this->heyuBinding->isThreadRunning() ) {
		Log(LOG_WARNING, "~HeyuBundle() : Destroying while RUNNING! Trying to stop.." );
		this->heyuBinding->quitThread(30000);
	 }
	delete this->heyuBinding; this->heyuBinding = NULL;
	Log(LOG_VERBOSE, ":~HeyuBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT HeyuBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;

	BusDevices.setMessageHandler(this);
	WRESULT bus_d_connected = BusDevices.connect( _Bus_Devices, Bus::ACTION_DEFERRED );
	if ( WFAILED(bus_d_connected) )
		Log(LOG_CRITICAL, ":HeyuBundle() : FAILED#%d Connecting Bus ("_Bus_Devices")..", bus_d_connected );

	int registerAliases = Properties.getValueOf( _Heyu_KEY_AutoReloadAliases ).toInteger(-1);
	if ( registerAliases != 0 )
		this->heyuBinding->heyuGetAliases();

	registerDevices();

	WRESULT ret = BundleGeneric::start_SynchThread( this->heyuBinding );

	// BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: IThreadListener::thread_event()
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT HeyuBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;

	WRESULT ret = BundleGeneric::stop_SynchThread( this->heyuBinding, 30000 );

	unRegisterDevices();

	// BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: IThreadListener::thread_event()
	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void HeyuBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;

	if ( message.getSourceBus() == _Bus_Core && message.getDestinationBus() == _Bus_Devices ) 
		return;
	if ( message.getSourceBus() == _Bus_Devices && message.getDestinationBus() == _Bus_Core ) 
		return;

	if ( MessageFilter::isMessageTarget(message, this) ) {
		BundleGeneric::busMessage(message, source);
		return;
	 }
	// let's support also :Devices/<NAME>
	else if ( message.getDestination().getPath().empty() || message.getDestination().getPath().at(0) != "Devices" ) {
		return;
	 }

	///@bug this implementation doesn't support regex & expressions
	std::string dev_name = message.getDestination().getName();
	DeviceX10* dev = NULL;

	this->heyuBinding->getDevices().transactionBeginRead();

	tDeviceX10List::ConstIterator it;
	const tDeviceX10List::ConstIterator it_end = this->heyuBinding->getDevices().end();
	for ( it=this->heyuBinding->getDevices().begin(); it!=it_end; ++it ) {
		if ( *it == NULL ) continue;
		if ( (*it)->getName() != dev_name ) continue;
		dev = *it;
		break;
	 }

	if ( dev == NULL ) {
		this->heyuBinding->getDevices().transactionEnd();
		if ( !message.getDestination().isPathAny() ) // let's not flood
			Log(LOG_VERBOSE, ":busMessage() = Message-Request for UNKNOWN Virtual-Child '%s'", dev_name.c_str() );
		return;
	 }

	Message* reply = busMessageDevice( message, dev );//no mutex

	this->heyuBinding->getDevices().transactionEnd();

	if ( reply == NULL ) return;

	if ( message.getCurrentBus() == BusCore.getBusName() )
		BusCore.postMessage(reply);
	else if ( message.getCurrentBus() == BusDevices.getBusName() )
		BusDevices.postMessage(reply);

 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Message* HeyuBundle::busMessageDevice( const Message& message, DeviceX10* dev )//no mutex
 {
	if ( dev == NULL ) return NULL;
	if ( message.isEmpty() ) return NULL;
	if ( !message.getContent()->isRequest() ) return NULL;
	const Request* request = message.getContent()->asRequest();
	Response* response = NULL;

	// map (proxy) methods and properties [REQUESTs]
	Log(LOG_VERBOSE, ":busMessageDevice() = Message-Request for Virtual-Child '%s'", dev->getName().c_str() );

	const Permission* devPerm = &((const DeviceX10*)dev)->getPermission();

	// ------------------------------------------------------------------------------------------------------- GET PROP
	if ( request->getMethod() == _METHOD_GetProperty ) {
		string key = request->getArgument().toString();
		Variant data = dev->getProperty( key );
		if ( data.isEmpty() ) // means not found..
			response = request->replyResponse(WRET_ERR_PARAM, "Property not found" );
		else
			response = request->replyResponse(WRET_OK, new Property(key, data, *devPerm) );
	 }
	// ------------------------------------------------------------------------------------------------------- SET PROP
	else if ( request->getMethod() == _METHOD_SetProperty ) {

	 }
	// ------------------------------------------------------------------------------------------------------- LIST PROP
	else if ( request->getMethod() == _METHOD_ListProperties ) {
		string option = request->getArgument().toString();

		std::vector<string> result;
		if ( option == "l" || option == "long" ) {
			result = dev->getKeys();
			std::vector<std::string>::iterator it;
			for ( it=result.begin(); it != result.end(); it++ )
				*it += " = " + dev->getProperty(*it).toString();
		 }
		else
			result = dev->getKeys();

		List* props = new List();
		props->fromVector( result );
		response = request->replyResponse(WRET_OK, props );
	 }
	// ------------------------------------------------------------------------------------------------------- LIST METHODS
	else if ( request->getMethod() == _METHOD_ListMethods ) {
		List* methods = new List();
		methods->push_back<Variant>( _METHOD_GetProperty );
		methods->push_back<Variant>( _METHOD_SetProperty );
		methods->push_back<Variant>( _METHOD_ListProperties );
		methods->push_back<Variant>( _METHOD_ListMethods );
		methods->push_back<Variant>( _METHOD_ListInterfaces );
		
		///@todo implement better here
		dev->getMethods( *methods );

		response = request->replyResponse(WRET_OK, methods );
	 }
	// ------------------------------------------------------------------------------------------------------- LIST INTERFACES
	else if ( request->getMethod() == _METHOD_ListMethods ) {
		List* interfaces = new List();
		interfaces->push_back<Variant>(  wosh::interfaces::devices::Switch::bundleType() );
		response = request->replyResponse(WRET_OK, interfaces );
	 }
	// -------------------------------------------------------------------------------------------------------
	// -------------------------------------------------------------------------------------------------------
	else if ( request->getMethod() == _Switch_METHOD_on ) {
		WRESULT ret = this->heyuBinding->turnUnitOn( dev->getX10Code() );
		response = request->replyResponse(ret);
	 }
	// -------------------------------------------------------------------------------------------------------
	else if ( request->getMethod() == _Switch_METHOD_off ) {
		WRESULT ret = this->heyuBinding->turnUnitOff( dev->getX10Code() );
		response = request->replyResponse(ret);
	 }
	// -------------------------------------------------------------------------------------------------------
	else if ( request->getMethod() == _Dimmer_METHOD_dim || request->getMethod() == _Dimmer_METHOD_bright ) {
		int value = request->getArgument().toInteger(0);
//qDebug() << value;
//		Utilities::fromString<int>(value_str, value);
		if ( request->getMethod() == _Dimmer_METHOD_dim )
			value = -value;

		WRESULT ret = this->heyuBinding->setUnitValueOffset( dev->getX10Code(), value );
		response = request->replyResponse(ret);
	 }
	// -------------------------------------------------------------------------------------------------------
	else if ( request->getMethod() == _Dimmer_METHOD_set ) {
		string value_str = request->getArgument().toString();
		int sign = 0;
		int value = -1;
		if ( value_str.find("+") == 0 ) {
			value = Utilities::fromString<int>(value_str.substr(1));
			sign = 1;
		 }
		else if ( value_str.find("-") == 0 ) {
			value = Utilities::fromString<int>(value_str.substr(1));
			sign = -1;
		 }
		else
			value = request->getArgument().toInteger(0);

		WRESULT ret = WRET_ERR_PARAM;
		if ( sign == 0 )
			ret = this->heyuBinding->setUnitValue( dev->getX10Code(), value );
		else
			ret = this->heyuBinding->setUnitValueOffset( dev->getX10Code(), sign*value );
		response = request->replyResponse(ret);
	 }
	// -------------------------------------------------------------------------------------------------------
	// -------------------------------------------------------------------------------------------------------
	else {
		Log(LOG_WARNING, ":busMessage() FAILED : Method '%s' not found", request->getMethod().c_str() );
		// should eval rights..
		response = request->replyResponse(WRET_ERR_PARAM, "Method not found!" );
		//return;
	 }

	if ( response == NULL )
		return NULL;

	Message* reply = message.replyTo(response);

	return reply;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS

bool HeyuBundle::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source )
 {
	if ( property_current.getKey() == _Heyu_KEY_HeyuBin ) {
		this->heyuBinding->setHeyuBinary( value_proposed.toString() );
		return true;
	 }
	else if ( property_current.getKey() == _Heyu_KEY_AutoReloadAliases ) {
		this->heyuBinding->setAutoReloadAliases( value_proposed.toInteger(-1) );
		return true;
	 }
	else if ( property_current.getKey() == _Heyu_KEY_RefreshFrequency ) {
		this->heyuBinding->setRefreshFrequency( value_proposed.toInteger(-1) );
		return true;
	 }
	else // forward processing to base class
		return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////// DRIVERX10 EVENTS

void HeyuBundle::driver_event( int event_id, HeyuBinding* ) {
	Log(LOG_VERBOSE, ":driver_event() : Event=%d", event_id );
	switch(event_id) {
		case HeyuBinding::ENGINE_RECONFIGURATION: {
			// let's restart the bundle.. ASYNCH!
			Log(LOG_WARNING, ":driver_event() : Reloading Devices due to heyu reconfiguration" );
			reloadHeyuDevices();
			break;
		 }
		default: break;
	 }
 }

void HeyuBundle::device_event( DeviceX10* device, DeviceX10::MODULE_ACTION event, int arg_value, int event_source, HeyuBinding* ) {
	if ( device == NULL ) {
		Log(LOG_WARNING, ":device_event(NULL) : Event=%d Arg=%d Src=%d", event, arg_value, event_source );
		return;
	 }
	int old_state = device->getX10State();
	Log(LOG_VERBOSE, ":device_event(%s) : Event=%d Arg=%d Src=%d [OldState=%d]", device->getX10Code().c_str(), event, arg_value, event_source, old_state );
	device->setX10State(arg_value);
	Message* message = new Message( new Fact( _Device_EVENT_StateChanged, new Variant(arg_value) ) );
	message->setSource( device );
	message->setDestinationBroadcast(true);
	message->setDestinationBus( _Bus_Devices ); //let's not flood other busses, we are @ low level
	SecurityManager::getCredentialImplicit().signMessage(message, this);
	BusDevices.postMessage( message );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT HeyuBundle::reloadHeyuDevices() {
	Log(LOG_VERBOSE, ":reloadHeyuDevices()" );
	unRegisterDevices();
	this->heyuBinding->heyuGetAliases();
	this->heyuBinding->updateAll();
	registerDevices();
	return WRET_OK;
 }

void HeyuBundle::updateHeyuProps() {
	Log(LOG_VERBOSE, ":updateHeyuProps()" );
	Properties.transactionBeginWrite();
	Properties.update( _Heyu_KEY_HeyuStatus,		(int)this->heyuBinding->getState() );
	Properties.update( _Heyu_KEY_HeyuArmedState,	this->heyuBinding->getHeyuArmedState() );
	Properties.update( _Heyu_KEY_HeyuVersion,		this->heyuBinding->getHeyuVersion() );
	Properties.update( _Heyu_KEY_HeyuConfigFile,	this->heyuBinding->getHeyuConfigFile() );
	Properties.update( _Heyu_KEY_HeyuPowerLineInt,	this->heyuBinding->getHeyuPowerLineInt() );
	Properties.transactionEnd();
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
void HeyuBundle::registerDevices() { // must be synch!
	bool bindAlias = Properties.getValueOf( _Heyu_KEY_BindAliasesAsName ).toBoolean(true);
	bool registerAll = Properties.getValueOf( _Heyu_KEY_RegisterAll ).toBoolean(false);
	this->heyuBinding->getDevices().transactionBeginWrite();
	Log(LOG_INFO, ":registerDevices() %d devices [bindAlias: %d; regAll: %d]", this->heyuBinding->getDevices().size(), bindAlias, registerAll );
	std::vector<Device*> devices;
	tDeviceX10List::Iterator it;
	const tDeviceX10List::ConstIterator it_end = this->heyuBinding->getDevices().end();
	for ( it=this->heyuBinding->getDevices().begin(); it!=it_end; ++it ) {
		if ( *it == NULL ) continue;
		if ( !registerAll && (*it)->getAlias() == "" )
			continue;
		Log(LOG_VERBOSE, ":registerDevices(%s) [%s]", (*it)->getName().c_str(), (*it)->getAlias().c_str() );
		(*it)->getURI().setKernelName( this->getURI().getKernelName() );
		(*it)->getURI().setName( (*it)->getName() ); // force the uri!
		Device* dev = new Device( (const Object&)(*it) );

		if ( bindAlias )
			dev->setName( (*it)->getAlias() );

		if ( dev->getName().empty() )
			dev->setName( (*it)->getX10Code() );

		//dev->setType( (*it)->getInterfaces().getNames().back() );
		dev->setPermission( this->getPermission() );
		dev->setBundleOwner( this->getName() );
		dev->getInterfaces() = (*it)->getInterfaces(); // copy
		devices.push_back( dev );
	 }
	this->heyuBinding->getDevices().transactionEnd();
	WoshKernel::getInstance()->devices().registerDevices( devices );
 }

void HeyuBundle::unRegisterDevices() { // must be synch!
	bool bindAlias = Properties.getValueOf( _Heyu_KEY_BindAliasesAsName ).toBoolean(true);
	bool registerAll = Properties.getValueOf( _Heyu_KEY_RegisterAll ).toBoolean(true);
	Log(LOG_INFO, ":unRegisterDevices() %d devices [bindAlias: %d; regAll: %d]", this->heyuBinding->getDevices().size(), bindAlias, registerAll );
	WoshKernel::getInstance()->devices().unRegisterDevicesOf( this->getName() );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* HeyuBundle::mmDoUpdate( const Request* request ) {
	if ( request == NULL ) return NULL;
	Log(LOG_VERBOSE, ":mmDoUpdate()" );
	WRESULT ret = WRET_OK;
	updateHeyuProps();
	this->heyuBinding->heyuGetAliases();
	this->heyuBinding->updateAll();
	// answer the request, also decorate response with current state (should be same just set)
	return request->replyResponse(ret, "TODO");
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* HeyuBundle::mmDoEvalCommand( const Request* request ) {
	if ( request == NULL ) return NULL;
	string command = request->getArgument().toString();
	Log(LOG_VERBOSE, ":mmDoEvalCommand(%s)", command.c_str() );
	int ret = this->heyuBinding->run_x10_command( command );
	if ( ret == 0 )
		return request->replyResponse(WRET_OK);
	return request->replyResponse(WRET_ERR_INTERNAL);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
